import { PayloadAction, createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { RootState } from "./store";
import { getRequest } from "../util/Auth";
import { NulResponse, createEmptyRequest, createRequest } from "./common";

interface InventoryEntry {
  id: number;
  name: string;
  quantity: number;
  supplierName: string;
  costPrice: number;
  retailPrice: number;
  wholesalePrice: number;
}

interface Warehouse {
  id: number;
  name: string;
}

interface InventoryState {
  fetched: boolean;
  fetchedAvailableWarehouses: boolean;
  currentWarehouse: Warehouse;
  availableWarehouses: Warehouse[];
  entries: InventoryEntry[];
}

interface FetchInventoryRequest {
  warehouseId: number;
}

interface CreateInventoryRequest {
  id: number;
  quantity: number;
  supplierId: number;
  warehouseId: number;
}

interface DeleteInventoryRequest {
  id: number[];
  warehouseId: number;
}

interface ModifyInventoryRequest {
  id: number;
  quantity: number;
  warehouseId: number;
  supplierId: number;
}

interface TransferInventory {
  id: number;
  quantity: number;
  warehouseId: number;
}

interface TransferInventoryRequest {
  transferRequests: TransferInventory[];
  targetId: number;
}

const inventoryInitialState: InventoryState = {
  fetched: false,
  fetchedAvailableWarehouses: false,
  currentWarehouse: {
    id: -1,
    name: ''
  },
  availableWarehouses: [],
  entries: []
};

export const fetchAvailableWarehouses = createAsyncThunk('api/fetchAvailableWarehouses', async (_: void, { getState, rejectWithValue }) => {
  const state = getState() as RootState;
  try {
    const request = getRequest(state);
    const response = await request.post<NulResponse<Warehouse[]>>('/service/inventory/fetchAvailableWarehouses', createEmptyRequest());
    return response.data;
  }
  catch (e) {
    return rejectWithValue(e);
  }
})

export const fetchAllInventoryEntries = createAsyncThunk('api/fetchAllEntries', async (fetchRequest: FetchInventoryRequest, { getState, rejectWithValue }) => {
  const state = getState() as RootState;
  try {
    const request = getRequest(state);
    const response = await request.post<NulResponse<InventoryEntry[]>>('/service/inventory/fetchAll', createRequest(fetchRequest));
    return response.data;
  }
  catch (e) {
    return rejectWithValue(e);
  }
});

export const createInventory = createAsyncThunk('api/createInventory', async (inventoryRequest: CreateInventoryRequest, { getState, rejectWithValue }) => {
  const state = getState() as RootState;
  try {
    const request = getRequest(state);
    const response = await request.post<NulResponse<InventoryEntry>>('/service/inventory/createEntry',
      createRequest(inventoryRequest));
    return response.data;
  }
  catch (e) {
    return rejectWithValue(e);
  }
});

export const deleteInventory = createAsyncThunk('api/deleteInventory', async (deleteRequest: DeleteInventoryRequest, { getState, rejectWithValue }) => {
  const state = getState() as RootState;
  try {
    const request = getRequest(state);
    const response = await request.post<NulResponse<null>>('/service/inventory/deleteEntry',
      createRequest(deleteRequest));
    return response.data;
  }
  catch (e) {
    return rejectWithValue(e);
  }
});

export const modifyInventory = createAsyncThunk('api/modifyInventory', async (modifyRequest: ModifyInventoryRequest, { getState, rejectWithValue }) => {
  const state = getState() as RootState;
  try {
    const request = getRequest(state);
    const response = await request.post<NulResponse<null>>('/service/inventory/modifyEntry',
      createRequest(modifyRequest));
    return response.data;
  }
  catch (e) {
    return rejectWithValue(e);
  }
});

export const transferInventory = createAsyncThunk('api/transferInventory', async (transferRequest: TransferInventoryRequest, { getState, rejectWithValue }) => {
  const state = getState() as RootState;
  try {
    const request = getRequest(state);
    const response = await request.post<NulResponse<null>>('/service/inventory/transferEntry',
      createRequest(transferRequest));
    return response.data;
  }
  catch (e) {
    return rejectWithValue(e);
  }
});

const inventorySlice = createSlice({
  name: 'inventory',
  initialState: inventoryInitialState,
  reducers: {
    switchWarehouse: (state, action: PayloadAction<number>) => {
      const index: number = state.entries.findIndex((value) => value.id === action.payload);
      if (index !== -1) {
        state.currentWarehouse = state.availableWarehouses[index];
      }
    }
  },
  extraReducers: (builder) => {
    builder.addCase(fetchAllInventoryEntries.rejected, (state, action) => {
      console.error(action.error);
    }).addCase(fetchAllInventoryEntries.fulfilled, (state, action) => {
      state.fetched = true;
      state.entries = action.payload.result;
    }).addCase(createInventory.rejected, (state, action) => {
      console.error(action.error);
    }).addCase(createInventory.fulfilled, (state, action) => {
      state.entries.push(action.payload.result);
      state.entries.sort((a, b) => a.id - b.id);
    }).addCase(deleteInventory.rejected, (state, action) => {
      console.error(action.error);
    }).addCase(deleteInventory.fulfilled, (state, action) => {
      const deleteId: number[] = action.meta.arg.id;
      deleteId.sort();
      const result: InventoryEntry[] = [];
      for (let i = 0, j = 0; j < state.entries.length; ++j){
        if (deleteId[i] === state.entries[j].id) {
          ++i;
        } else {
          result.push(state.entries[j]);
        }
      }
      state.entries = result;
    }).addCase(modifyInventory.rejected, (state, action) => {
      console.error(action.error);
    }).addCase(modifyInventory.fulfilled, (state, action) => {
      const index: number = state.entries.findIndex((value) => value.id === action.meta.arg.id);
      if (index !== -1) {
        state.entries[index].quantity = action.meta.arg.quantity;
      }
    }).addCase(transferInventory.rejected, (state, action) => {
      console.error(action.error);
    }).addCase(transferInventory.fulfilled, (state, action) => {
      const requests: TransferInventory[] = action.meta.arg.transferRequests;
      requests.sort((a, b) => a.id - b.id);
      for (let i = 0, j = 0; j < state.entries.length; ++j) {
        if (requests[i].id === state.entries[j].id) {
          state.entries[j].quantity -= requests[i].quantity;
          ++i;
        }
      }
    }).addCase(fetchAvailableWarehouses.rejected, (state, action) => {
      console.error(action.error);
    }).addCase(fetchAvailableWarehouses.fulfilled, (state, action) => {
      state.fetchedAvailableWarehouses = true;
      state.availableWarehouses = action.payload.result;
      if (state.availableWarehouses !== null && state.availableWarehouses.length > 0) {
        state.currentWarehouse = state.availableWarehouses[0];
      }
    });
  }
});

export type { InventoryEntry, Warehouse, TransferInventoryRequest, TransferInventory };
export const { switchWarehouse } = inventorySlice.actions;
export const selectInventoryFetched = (state: RootState) => state.inventoryManagementReducer.fetched;
export const selectInventoryEntries = (state: RootState) => state.inventoryManagementReducer.entries;
export const selectInventoryAvailableWarehousesFetched = (state: RootState) => state.inventoryManagementReducer.fetchedAvailableWarehouses;
export const selectInventoryAvailableWarehouses = (state: RootState) => state.inventoryManagementReducer.availableWarehouses;
export const selectInventoryWarehouseId = (state: RootState) => state.inventoryManagementReducer.currentWarehouse.id;
export const selectInventoryWarehouseName = (state: RootState) => state.inventoryManagementReducer.currentWarehouse.name;
export default inventorySlice.reducer;